package Seletores;

import java.util.ArrayList;
import java.util.HashMap;

import Util.AMUtil;

public class BaseProbabilidadeContinua {

	private ArrayList<String[]> base;

	private HashMap<String, Double> probabilidades;
	private double[] entropia;
	private double[] media;
	private double[][] covariancia;
	private HashMap<String, Double> infMutuaCondicional;
	private HashMap<String, Double> infMutua;
	public final double H = 1;
	
	public BaseProbabilidadeContinua(ArrayList<String[]> base) {
		this.base = base;
		this.probabilidades = new HashMap<String, Double>();
		this.entropia = new double[base.get(0).length];
		this.media = new double[base.get(0).length];
		this.covariancia = new double[base.get(0).length][base.get(0).length];
		this.infMutuaCondicional = new HashMap<String, Double>();
		this.infMutua = new HashMap<String, Double>();
	}
	
	public double probabilidadeCondicional(int feature1, double value1,int feature2, double value2, String classe){
		
		String key = "f" + feature1 + "v" + value1 + "f" + feature2 + "v" + value2 + "c" + classe;
		
		if( !probabilidades.containsKey(key) ){
			String key2 = "f" + feature2 + "v" + value2 + "f" + feature1 + "v" + value1 + "c" + classe;
			double soma = 0;
			int contador = 0;
			for (String[] linha : base) {
				if(linha[linha.length - 1].equals(classe) ){
					contador++;
					String valueI1 = linha[feature1];
					String valueI2 = linha[feature2];
					if(isNumeric(valueI1) && isNumeric(valueI2) ){
						double v1 = Double.valueOf(valueI1);
						double v2 = Double.valueOf(valueI2);
						double[] w = new double[2];
						w[0] = value1 - v1;
						w[1] = value2 - v2;
						soma += janelaParsen(feature1, feature2, w, H);
					}
				}
			}
			probabilidades.put(key,  soma / (double) (contador) );
			probabilidades.put(key2,  soma / (double) (contador) );
		}
		return probabilidades.get(key);
	}
	
	public double probabilidadeCondicional(int feature, double value, String classe){
		
		String key = "f" + feature + "v" + value + "c" + classe;
		if( !probabilidades.containsKey(key) ){
			double soma = 0;
			int contador = 0;
			for (String[] linha : base) {
				if(linha[linha.length - 1].equals(classe) ){
					contador++;
					String valueI = linha[feature];
					if(isNumeric(valueI)){
						double v = Double.valueOf(valueI);
						soma += janelaParsen(feature, (value - v), H);
					}
				}
			}
			probabilidades.put(key,  soma / (double) (contador) );
		}
		return probabilidades.get(key);
	}
	
	public double getInformacaoMutua( int feature1, int feature2 ){
		ArrayList<String> valoresPossiveis1 = AMUtil.histogramaKeys(base, feature1);
		ArrayList<String> valoresPossiveis2 = AMUtil.histogramaKeys(base, feature2);
		
		
		return 0;
	}
	
	public double probabilidade(int feature1, double value1,int feature2, double value2){
		
		String key = "f" + feature1 + "v" + value1 + "f" + feature2 + "v" + value2;
		
		if( !probabilidades.containsKey(key) ){
			String key2 = "f" + feature2 + "v" + value2 + "f" + feature1 + "v" + value1;
			double soma = 0;
			for (String[] linha : base) {
				String valueI1 = linha[feature1];
				String valueI2 = linha[feature2];
				if(isNumeric(valueI1) && isNumeric(valueI2) ){
					double v1 = Double.valueOf(valueI1);
					double v2 = Double.valueOf(valueI2);
					double[] w = new double[2];
					w[0] = value1 - v1;
					w[1] = value2 - v2;
					soma += janelaParsen(feature1, feature2, w, H);
				}
			
			}
			probabilidades.put(key,  soma / (double) ( base.size() ) );
			probabilidades.put(key2,  soma / (double) ( base.size() ) );
		}
		return probabilidades.get(key);
	}
		
	
	public double probabilidade(int feature, double value ){
		
		String key = "f" + feature + "v" + value;
		if( !probabilidades.containsKey(key) ){
			double soma = 0;
			for (String[] linha : base) {
				String valueI = linha[feature];
				if(isNumeric(valueI)){
					double v = Double.valueOf(valueI);
					soma += janelaParsen(feature, (value - v), H);
				}
			}
			probabilidades.put(key,  soma / (double) (base.size() - 1) );
		}
		return probabilidades.get(key);
	}
	
	public double janelaParsen(int feature, double w, double h){
		
		double covariancia = calculaCovariancia(feature, feature);
		double expoente = - (w * (1/covariancia) * w) / 2 * Math.pow(h, 2);
		
		double fator = 1 / ( Math.sqrt( 2 * Math.PI ) * h *  Math.sqrt( covariancia ) );
		
 		return fator * Math.exp(expoente);
	}
	
	public double janelaParsen(int feature1, int feature2, double[] w, double h){
		
		double matCov[][] = calculaMatrizCovariancia(feature1, feature2);
		double matInvCov[][] = calculaInversa(matCov);
		
		double expoente = w[0] * w[0] * matInvCov[0][0]
							+ w[0] * w[1] * matInvCov[1][0]
							+ w[0] * w[0] * matInvCov[0][1]
							+ w[1] * w[1] * matInvCov[1][1];
		expoente = 	- expoente / ( 2 * Math.pow(h, 2) );
		
		double fator = 1 / ( ( 2 * Math.PI ) * Math.pow(h, 2) *  Math.sqrt( calculaDeterminante2x2(matCov) ) );
		
 		return fator * Math.exp(expoente);
	}
	
	private double[][] calculaMatrizCovariancia(int feature1, int feature2){
		double cov[][] = new double[2][2];
		cov[0][0] = calculaCovariancia(feature1, feature1);
		cov[1][1] = calculaCovariancia(feature2, feature2);
		cov[0][1] = calculaCovariancia(feature1, feature2);
		cov[1][0] = cov[0][1];
		return cov;
	}
	
	private double calculaCovariancia(int feature1, int feature2){
		if(covariancia[feature1][feature2] == 0){
			double media1 = getMedia(feature1);
			double media2 = getMedia(feature2);
			double soma = 0;
			for (String[] linha : base) {
				String value1 = linha[feature1];
				String value2 = linha[feature2];
				if(isNumeric(value1) && isNumeric(value2) ) soma += ( Double.valueOf(value1) - media1 ) * ( Double.valueOf(value2) - media2 ) ;
			}
			covariancia[feature1][feature2] = soma / (double)(base.size() - 1);
		}
		return covariancia[feature1][feature2];
	}
	
	private double calculaDeterminante2x2(double mat[][]){
		return mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1];
	}
	
	private double[][] calculaInversa(double matriz[][]){
		
		double inv[][] = new double[2][2];
		
		inv[0][0] = - matriz[1][1] / (matriz[0][1]*matriz[1][0] - matriz[1][1]*matriz[0][0]);
		inv[0][1] = - matriz[0][1]/(matriz[1][1]*matriz[0][0] - matriz[0][1]*matriz[1][0]);
		inv[1][0] = matriz[1][0]/(matriz[0][1]*matriz[1][0] - matriz[1][1]*matriz[0][0]);
		inv[1][1] = matriz[0][0]/(matriz[1][1]*matriz[0][0] - matriz[0][1]*matriz[1][0]);
		
		return inv;
	}
	
	public double getMedia(int feature){
		if(media[feature] == 0){
			double soma = 0;
			for (String[] linha : base) {
				String value = linha[feature];
				if(isNumeric(value)) soma += Double.valueOf(value);
			}
			media[feature] = soma/(double)base.size();
		}
		return media[feature];
	}
	
	private boolean isNumeric(String n){
		boolean result = true;
		for (int i = 0; i < n.length(); i++) {
			if( !Character.isDigit( n.charAt(i) ) ){
				result = false;
				break;
			}
		}
		return result;
	}

	public ArrayList<String[]> getBase() {
		return base;
	}
	
}
